﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
using System.Security.Principal;
using System.Windows.Forms;
using ActiveDs;
using Library;

namespace Directory
{
  /// <summary>
  /// グループリスト画面のフォームです。
  /// </summary>
  internal sealed partial class GroupList : Form
  {
    #region プライベートフィールド
    //プライマリ グループのメンバー格納用(グループのプライマリ グループ トークン, メンバーのパスとオブジェクトの種類のコレクション)
    private readonly Dictionary<int, Dictionary<string, CategoryType>> primaryGroupMembers;
    private readonly BindingSource groupBindingSource;
    #endregion

    #region パブリックコンストラクター
    /// <summary>
    /// GroupList クラスの新しいインスタンスを初期化します。
    /// </summary>
    public GroupList()
    {
      InitializeComponent();

      IEnumerable<IGroup> groups;   //グループのリスト
      if (DirectoryAccess.CanConnectDomain)   //ドメインに接続できる時
      {
        groups = DirectoryAccess.GetGroups<DomainGroup>();  //グループのリストを取得
        groupBindingSource = this.DomainGroupBindingSource;
        this.LocalPanel.Visible = false;
      }
      else  //ドメインに接続できない時
      {
        groups = DirectoryAccess.GetGroups<LocalGroup>();   //グループのリストを取得
        groupBindingSource = this.LocalGroupBindingSource;
        this.DomainPanel.Visible = false;
        this.GroupListBox.Height -= 96;
        this.Height -= 166;
      }
      primaryGroupMembers = new Dictionary<int, Dictionary<string, CategoryType>>();
      groupBindingSource.DataSource = groups;
      this.DescriptionTextBox.DataBindings.Add("Text", groupBindingSource, "Description");
      this.GroupListBox.DataSource = groupBindingSource;
      this.CountLabel.Text = String.Format("{0} 個のオブジェクト", groupBindingSource.Count);
    }
    #endregion

    #region イベントハンドラ
    private void GroupListBox_SelectedIndexChanged(object sender, EventArgs e)
    {
      if (this.GroupListBox.SelectedIndex == -1)
      {
        return;
      }

      this.ShowRelatingInfo();  //関連情報を表示
      this.InRoleButton.Enabled = this.GroupListView.Items.Count > 0;
      this.MemberButton.Enabled = this.MemberListView.Items.Count > 0;
    }

    private void InRoleButton_Click(object sender, EventArgs e)
    {
      var group = (DomainGroup)groupBindingSource.Current;  //選択されたグループ
      using (var fm = new InRoleGroupList(group))   //所属している全グループ画面
      {
        fm.ShowDialog(this);
      }
    }

    private void MemberButton_Click(object sender, EventArgs e)
    {
      var group = (DomainGroup)groupBindingSource.Current;  //選択されたグループ
      using (var fm = new AllMemberList(group))   //すべてのメンバー画面
      {
        fm.ShowDialog(this);
      }
    }
    #endregion

    #region プライベートメソッド
    /// <summary>
    /// 関連情報を表示します。
    /// </summary>
    private void ShowRelatingInfo()
    {
      var group = (IGroup)groupBindingSource.Current;   //選択されたグループ
      if (DirectoryAccess.CanConnectDomain)   //ドメインに接続できる時
      {
        this.ShowDomainMembers(group.Native);   //ドメイングループのメンバーを表示
        this.ShowBelongGroups(group.Entry);     //所属するグループを表示
      }
      else  //ドメインに接続できない時
      {
        this.ShowLocalMembers(group.Native);  //ローカルグループのメンバーを表示
      }
    }

    /// <summary>
    /// ドメイングループのメンバーを表示します。
    /// </summary>
    /// <param name="group">グループ。</param>
    private void ShowDomainMembers(IADsGroup group)
    {
      string[] subItem = new string[2];   //名前と所属パスの配列
      CategoryType objectType;    //ディレクトリ オブジェクトの種類

      this.MemberListView.Items.Clear();
      foreach (IADs member in group.Members())
      {
        objectType = (CategoryType)Enum.Parse(typeof(CategoryType), member.Class, true);
        if (objectType == CategoryType.ForeignSecurityPrincipal)  //外部のセキュリティ プリンシパルの時
        {
          var objectSid = (byte[])member.Get("objectSid");
          var sid = new SecurityIdentifier(objectSid, 0);   //SID
          var account = (NTAccount)sid.Translate(typeof(NTAccount));  //アカウントに変換
          subItem[0] = account.Value;   //NT AUTHORITY\○○ => ToStringメソッドでも同じ
        }
        else  //外部のセキュリティプリンシパル以外の時
        {
          subItem[0] = member.Get("cn").ToString();   //名前を取得
        }
        subItem[1] = DirectoryAccess.GetBelongPath(member);    //所属パスを取得
        this.MemberListView.Items.Add(new ListViewItem(subItem, (int)objectType));  //リストビューに項目を追加
      }
      this.AddPrimaryGroupMembers();  //プライマリ グループになっているメンバーを追加
    }

    /// <summary>
    /// プライマリ グループになっているメンバーを追加します。
    /// </summary>
    private void AddPrimaryGroupMembers()
    {
      var token = ((DomainGroup)groupBindingSource.Current).Token;  //選択されたグループのプライマリ グループ トークン
      Dictionary<string, CategoryType> primaryMembers;    //プライマリ グループのメンバー(パスとオブジェクトの種類のコレクション)

      if (primaryGroupMembers.TryGetValue(token, out primaryMembers))   //格納済の時
      {
        if (primaryMembers.Count == 0)
        {
          return;
        }
      }
      else    //未格納の時
      {
        //プライマリ グループ トークンのグループをプライマリ グループとしているメンバーのDirectoryEntryのコレクションを取得
        var primaryMemberEntries = DirectoryAccess.GetPrimaryGroupMemberEntries(token);
        primaryMembers = new Dictionary<string, CategoryType>(primaryMemberEntries.Count);
        primaryGroupMembers.Add(token, primaryMembers);
        if (primaryMemberEntries.Count == 0)
        {
          return;
        }

        foreach (var user in primaryMemberEntries)  //取得メンバー数分
        {
          primaryMembers.Add(user.Path, (CategoryType)Enum.Parse(typeof(CategoryType), user.SchemaClassName, true));
          user.Close();
        }
      }

      string[] subItem = new string[2];   //名前と所属パスの配列
      foreach (var member in primaryMembers)  //プライマリ グループになっているメンバー数分
      {
        subItem[0] = DirectoryAccess.PathToCn(member.Key);    //名前を取得
        subItem[1] = DirectoryAccess.GetBelongPath(member.Key);   //所属パスを取得
        this.MemberListView.Items.Add(new ListViewItem(subItem, (int)member.Value));  //リストビューに項目を追加
      }
    }

    /// <summary>
    /// 所属するグループを表示します。
    /// </summary>
    /// <param name="group">グループ。</param>
    private void ShowBelongGroups(DirectoryEntry group)
    {
      string[] subItem = new string[2];   //名前と所属パスの配列
      this.GroupListView.Items.Clear();
      foreach (string groupPath in group.Properties["memberOf"])
      {
        subItem[0] = DirectoryAccess.PathToCn(groupPath);   //名前を取得
        subItem[1] = DirectoryAccess.GetBelongPath(groupPath);  //所属パスを取得
        this.GroupListView.Items.Add(new ListViewItem(subItem));  //リストビューに項目を追加
      }
    }

    /// <summary>
    /// ローカルグループのメンバーを表示します。
    /// </summary>
    /// <param name="group">グループ。</param>
    private void ShowLocalMembers(IADsGroup group)
    {
      var members = group.Members().Cast<IADs>().Select(member => GetLocalMemberName(member)).ToList();
      this.MemberListBox.DataSource = members;
    }

    /// <summary>
    /// ローカルのメンバー名を取得します。
    /// </summary>
    /// <param name="member">メンバー。</param>
    /// <returns>メンバー名。</returns>
    private string GetLocalMemberName(IADs member)
    {
      var eidx = member.ADsPath.LastIndexOf('/') - 1;
      var sidx = member.ADsPath.LastIndexOf('/', eidx) + 1;
      var pcname = member.ADsPath.Substring(sidx, eidx - sidx + 1);

      if (pcname.Equals(Environment.UserDomainName, StringComparison.CurrentCultureIgnoreCase))   //ローカルユーザーの時
      {
        return member.ADsPath.Substring(member.ADsPath.LastIndexOf('/') + 1);
      }
      else    //ローカルユーザー以外の時
      {
        return member.ADsPath.Substring(sidx).Replace('/', '\\');
      }
    }
    #endregion
  }
}
